import 'dart:convert';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:rt_fs/rtc/utils/turn.dart';

import 'dart:html';

import 'package:rt_fs/rtc/utils/websocket.dart';

import '../utils/device_info.dart';



class Signaling {
  JsonEncoder _encoder = JsonEncoder();
  JsonDecoder _decoder = JsonDecoder();

  Signaling(this._host,this._token,this._context,CallStreamBack callStreamBack,{required this.callStateBack});
  var _host;
  var _port = 8086;

  var _turnCredential;

  BuildContext? _context;
  CustomWebSocket? _socket;

  String _selfId = "10243"; //登录人
  String _token;

  Map<String, Session> _sessions = {};
  MediaStream? _localStream;

  List<RTCRtpSender> _senders = <RTCRtpSender>[];


  CallStateBack callStateBack;

  CallStreamBack? callStreamBack;

  Map<String, dynamic> _iceServers = {
    'iceServers':[
      {
        'url': 'stun:stun.l.google.com:19302'
      }
    ]
  };

  final Map<String, dynamic> _config = {
    'mandatory': {},
    'optional': [
      {'DtlsSrtpKeyAgreement': true},
    ]
  };

  final Map<String, dynamic> _dcConstraints = {
    'mandatory': {
      'OfferToReceiveAudio': false,
      'OfferToReceiveVideo': false,
    },
    'optional': [],
  };

  Future<void> connect() async {
    var url = 'https://$_host:$_port/ws';
    _socket = CustomWebSocket(url, _token);
    if(_turnCredential == null) {
      try {
        _turnCredential = await getTurnCredential(_host, _port);
        _iceServers = {
          'iceServers': [
            {
              'urls': _turnCredential['uris'][0],
              'username': _turnCredential['username'],
              'credential': _turnCredential['password']
            }
          ]
        };
      } catch (e) {

      }
    }

    //首次连接socket
    _socket?.onOpen = (){
      _send('new', {
        'name': DeviceInfo.label,
        'id': _selfId,
        'user_agent': DeviceInfo.userAgent
      });
    };

    _socket?.onMessage = (message){
      onMessage(_decoder.convert(message));
    };

    _socket?.onClose = (int? code, String? reason){
      print('Closed by server [$code => $reason]!');
    };

    await _socket?.connect();
  }

  void onMessage(message) async {
    Map<String, dynamic> mapData = message;
    var data = mapData['data'];
    String type = data['type'];
    switch(type){
      case 'peers': //当前连接到服务器的所有人
        break;
      case 'offer': //邀请通话  显示在接电话人界面 被邀请人接受到消息
        {
          var peerId = data['from'];
          var description = data['description'];
          var media = data['media'];
          var sessionId = data['session_id'];
          var session = _sessions[sessionId];
          var newSession = await _createSession(session, peerId: peerId, sessionId: sessionId, media: media);
          _sessions[sessionId] = newSession;
          await newSession.pc?.setRemoteDescription(
              RTCSessionDescription(description['sdp'], description['type']));
          if (newSession.remoteCandidates.length > 0) {
            newSession.remoteCandidates.forEach((candidate) async {
              await newSession.pc?.addCandidate(candidate);
            });
            newSession.remoteCandidates.clear();
          }
          callStateBack.stateNew!(sessionId);
          callStateBack.stateRinging!(newSession);
        }
        break;
      case 'answer': //应答   显示在发起人界面 发起人接受到消息
        {
          var description = data['description'];
          var sessionId = data['session_id'];
          var session = _sessions[sessionId];
          session?.pc?.setRemoteDescription(
              RTCSessionDescription(description['sdp'], description['type']));
          callStateBack.stateConnected!(session!);//发起成功 发起页面
        }
        break;
      case 'candidate'://尝试ice候选人完成 这个看网络 尝试时间不定
        {
          var peerId = data['from'];
          var candidateMap = data['candidate'];
          var sessionId = data['session_id'];
          var session = _sessions[sessionId];
          RTCIceCandidate candidate = RTCIceCandidate(candidateMap['candidate'],
              candidateMap['sdpMid'], candidateMap['sdpMLineIndex']);
          if(session != null){
            if(session.pc != null){
              await session.pc?.addCandidate(candidate);
            }else{
              session.remoteCandidates.add(candidate);
            }
          }else{
            _sessions[sessionId] = Session(sid: sessionId, pid: peerId)
                ..remoteCandidates.add(candidate);
          }
        }
        break;
      case 'leave': //断开连接
        {
          var peerId = data as String;
          _closeSessionByPeerId(peerId);
        }
        break;
      case 'bye': //挂断电话
        {
          var sessionId = data['session_id'];
          print('bye: ' + sessionId);
          var session = _sessions.remove(sessionId);
          if (session != null) {
            callStateBack.stateBye!();
            _closeSession(session);
          }
        }
        break;
      case 'keepalive': //保活
        break;
      default:
        break;
    }
  }

  /**
   * 创建本地视频流
   */
  Future<MediaStream> createStream(String media, {BuildContext? context}) async {
    final Map<String, dynamic> mediaConstraints = {
      'audio': true,
      'video': {
        'mandatory': {
          'minWidth': '640', // Provide your own width, height and frame rate here
          'minHeight': '480',
          'minFrameRate': '30',
        },
        'facingMode': 'user',
        'optional': [],
      }
    };
    MediaStream mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);

    callStreamBack?.onLocalStream!(mediaStream);
    return mediaStream;
  }



  //创建远程视频流
  String get sdpSemantics => 'unified-plan';
  Future<Session> _createSession(
        Session? session,{
          required String peerId,
          required String sessionId,
          required String media,
      }) async {
    var newSession = session ?? Session(sid: sessionId, pid: peerId);
    if(media != 'data'){
      _localStream = await createStream(media,context: this._context);
    }

    RTCPeerConnection pc = await createPeerConnection({
      ..._iceServers,
      ...{'sdpSemantics': sdpSemantics}
    },_config);

    if(media != 'data'){
      switch(sdpSemantics){
        case 'plan-b':
          pc.onAddStream = (MediaStream stream){
            callStreamBack?.onAddRemoteStream!(newSession, stream);
            //_remoteStreams.add(stream);
          };
          await pc.addStream(_localStream!);
          break;
        case 'unified-plan':
          pc.onTrack = (event){
            if(event.track.kind == 'video'){
              callStreamBack?.onAddRemoteStream!(newSession, event.streams[0]);
            }
          };
          _localStream!.getTracks().forEach((track) async {
            _senders.add(await pc.addTrack(track, _localStream!));
          });
          break;
        default:
          break;
      }
    }
    pc.onIceCandidate = (candidate) async {
      if (candidate == null) {
        print('onIceCandidate: complete!');
        return;
      }
      //需要此延迟，以便有足够的时间尝试ICE候选人
      await Future.delayed(const Duration(seconds: 1),() => {
        _send('candidate', {
          'to': peerId,
          'from': this._selfId,
          'candidate': {
            'sdpMLineIndex': candidate.sdpMLineIndex,
            'sdpMid': candidate.sdpMid,
            'candidate': candidate.candidate,
          },
          'session_id': sessionId,
        })
      });
    };
    pc.onConnectionState = (state){};

    //断开移除远程流
    pc.onRemoveStream = (stream){
      callStreamBack?.onRemoveRemoteStream!();
    };

    //字节流
    pc.onDataChannel = (channel) {

    };

    newSession.pc = pc;
    return newSession;
  }

  /**
   * 呼叫 peerId
   * media
   * 1.data
   * 2.video
   * 3.audio
   */
  void call(String peerId, String media) async {
    var sessionId = _selfId + '-' + peerId;
    Session session = await _createSession(null,
        peerId: peerId,
        sessionId: sessionId,
        media: media);
    _sessions[sessionId] = session;
    if (media == 'data') {
      _createDataChannel(session);
    }
    //发起offer邀请
    _createOffer(session, media);
    //开始等待...并通知等待界面
    callStateBack.stateNew!(session.sid);
    callStateBack.stateInvite!(session);
  }

  /**
   * 发出一个offer邀请
   */
  Future<void> _createOffer(Session session, String media) async {
    try{
      RTCSessionDescription s = await session.pc!.createOffer(media == 'data' ? _dcConstraints : {});
      await session.pc!.setLocalDescription(_fixSdp(s));
      _send('offer', {
        'to': session.pid,
        'from': _selfId,
        'description': {'sdp': s.sdp, 'type': s.type},
        'session_id': session.sid,
        'media': media,
      });
    }catch(e){
      print(e.toString());
    }
  }

  /**
   * 接受邀请
   */
  Future<void> _createAnswer(Session session, String media) async {
    try{
      RTCSessionDescription s = await session.pc!.createAnswer(media == 'data' ? _dcConstraints : {});
      await session.pc!.setLocalDescription(_fixSdp(s));
      _send('answer', {
        'to': session.pid,
        'from': _selfId,
        'description': {'sdp': s.sdp, 'type': s.type},
        'session_id': session.sid,
      });
    }catch(e){
      print(e.toString());
    }
  }

  RTCSessionDescription _fixSdp(RTCSessionDescription s) {
    var sdp = s.sdp;
    s.sdp = sdp!.replaceAll('profile-level-id=640c1f', 'profile-level-id=42e032');
    return s;
  }


  /**
   *
   */
  void _createDataChannel(Session session) async {

  }

  /***
   * 发送消息
   * type 类型
   */
  _send(type, data) {
    var request = Map();
    request["type"] = type;
    request["data"] = data;
    _socket?.send(_encoder.convert(request));
  }


  Future<void> _cleanSessions() async {
    if (_localStream != null) {
      _localStream!.getTracks().forEach((element) async {
        await element.stop();
      });
      await _localStream!.dispose();
      _localStream = null;
    }
    _sessions.forEach((key, sess) async {
      await sess.pc?.close();
      await sess.dc?.close();
    });
    _sessions.clear();
  }
  /**
   * 删除对方 session
   */
  void _closeSessionByPeerId(String peerId) {
    var session;
    _sessions.removeWhere((String key, Session sess) {
      var ids = key.split('-');
      session = sess;
      return peerId == ids[0] || peerId == ids[1];
    });
    if (session != null) {
      _closeSession(session);
    }
  }

  Future<void> _closeSession(Session session) async {
    _localStream?.getTracks().forEach((element) async {
      await element.stop();
    });

    await _localStream?.dispose();
    _localStream = null;

    await session.pc?.close();
    _senders.clear();
  }
}

abstract class CallStateBack {
  /**
   * 新的呼叫
   */
  Function(String sessionId)? stateNew;

  /**
   * 收到呼叫邀请 后续可操作是否接受
   */
  Function(Session session)? stateRinging;

  /**
   * 邀请等待中
   */
  Function(Session session)? stateInvite;

  /**
   * 已接受邀请
   * 连接成功
   */
  Function(Session session)? stateConnected;

  /**
   * 挂断通话
   */
  Function()? stateBye;
}

abstract class CallStreamBack {
  /**
   * 本地流推送
   */
  Function(MediaStream stream)? onLocalStream;

  /**
   * 远程流推送
   */
  Function(Session session, MediaStream stream)? onAddRemoteStream;

  /**
   * 远程流移除
   */
  Function()? onRemoveRemoteStream;
}

abstract class CallDataBack {
  Function(Session session, RTCDataChannel dc, RTCDataChannelMessage data)? onDataChannelMessage;

  Function(Session session, RTCDataChannel dc)? onDataChannel;
}

enum SignalingState {
  ConnectionOpen,
  ConnectionClosed,
  ConnectionError,
}




// enum VideoSource {
//   Camera,
//   Screen,
// }

class Session {
  Session({required this.sid, required this.pid});
  String pid;
  String sid;
  RTCPeerConnection? pc;
  RTCDataChannel? dc;
  List<RTCIceCandidate> remoteCandidates = [];
}